const http = require('http')
const WebSocket = require('ws')
const url = require('url')

class ws {

	constructor(common, config, redis, amqp) {
		this.common = common
		this.config = config
		const {port} = this.config.server
		this.redis = redis
		this.amqp = amqp
		this.wsPool = {}
		this.bindPool = {}
		this.server = http.createServer()
		this.server.on('upgrade', (request, socket, head) => {
			const urlObj = url.parse(request.url, true)
			const path = urlObj.pathname
			const data = urlObj.query
			if(this.wsPool[path]) {
				this.wsPool[path].svr.handleUpgrade(request, socket, head, (ws) => {
					ws.session = data.session
					this.wsPool[path].svr.emit('connection', ws, request)
				})
			}
			else
				socket.destroy();
		})
		this.server.listen(port)
		console.log(`WebSocket server is started, port:`, port)
	}

	router(path, connCallback, msgCallback) {
		const wss = new WebSocket.Server({ noServer: true })
		wss.path = path
		this.wsPool[path] = {svr: wss, conn: {}}
		const that = this
		wss.on('connection', function(conn) {
			that._auth(conn.session).then( async (user) => {
				if(!user) {
					conn.send(that._parseData('authError'))
					conn.close()
					return
				}
				const ch = await that.amqp.message.createChannel()
				conn.ch = ch
				conn.id = await that.common.createRandomStr(6)
				conn.user = user
				conn.path = this.path
				if(that.wsPool[this.path].conn[user.userId]) {
					console.error(`user ${user.userId} other devices connected`)
					that.wsPool[this.path].conn[user.userId].send(that._parseData('otherDevLogined'))
					that.wsPool[this.path].conn[user.userId].close()
				}
				conn.on('message', function(msg) {
					if(msg == '@heart') {
						this.send('@heart')
						return
					}
					msgCallback({
						ch,
						conn,
						user,
						msg,
						path: this.path,
						resolve: function(data) {
							this.conn.send(that._parseData(data))
						},
						reject: function(code, msg) {
							console.error(code, msg)
							this.conn.send(that._parseData(type, data))
						},
						parseMsg: function(msg) {
							const data = that.common.isJson(msg)
							if(!data) {
								console.error('invalid msg:', msg)
								this.conn.send(that._parseData('dataFormatInvalid'))
								return null
							}
							return data
						},
						redis: that.redis,
						amqp: that.amqp
					}).catch((err) => {
						console.error(err)
						conn.send(that._parseData('serverError'))
					})
				})
				conn.on('close', function(code, reason) {
					const {path, user} = this
					if(code != 1005)
						console.error(code, reason)
					conn.ch.close()
					if(that.wsPool[path].conn[user.userId] && this.id == that.wsPool[path].conn[user.userId].id)
						delete that.wsPool[path].conn[user.userId]
					console.log(`user ${user.userId} is close`)
				})
				conn.on('error', function(err) {
					console.error('connection error:', err)
				})
				that.wsPool[this.path].conn[user.userId] = conn
				console.log(`user ${user.userId} is connected`)
				connCallback({
					ch,
					conn,
					user,
					path: this.path,
					resolve: function(data) {
						this.conn.send(JSON.stringify(data))
					},
					reject: function(type = 'serverError', data) {
						this.conn.send(that._parseData(type, data))
					},
					parseMsg: function(msg) {
						const data = that.common.isJson(msg.content.toString())
						if(!data) {
							console.error('invalid msg:', msg.content.toString())
							this.conn.send(that._parseData('dataFormatInvalid'))
							return null
						}
						return data
					},
					redis: that.redis,
					amqp: that.amqp
				}).catch((err) => {
					console.error(err)
					conn.send(that._parseData('serverError'))
				})
			}).catch((err) => {
				console.error(err)
				conn.send(that._parseData('authError'))
				conn.close()
			})
		})
		wss.on('error', function(err) {
			console.log(`webSocket server ${this.path} is error:`, err)
		})
	}

	async _auth(session) {
		if(!session)
			return false
		const user = await this.redis.session.get(session, true)
		if(user) {
			if(!user.userId || !user.type || this.config.frame.allowUserType.indexOf(user.type) == -1)
				return false
			console.log('auth success', user)
			return user
		}
		else
			return false
	}

	_parseData(type, data = -1) {
		return JSON.stringify({
			type,
			data
		})
	}

}

module.exports = ws